{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The Qmod Workshop - Part 2: Higher-Level Concepts\n",
    "\n",
    "This is the second part of the Qmod workshop, covering exercises 6 through 10. Make sure to go through Part 1 before continuing with this notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 6 - Exponentiation and Pauli Operators\n",
    "\n",
    "The Qmod language supports different classical types: scalars, arrays, and structs. Structs are objects with member variables, or fields.\n",
    "\n",
    "See also [Classical Types](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/classical-types/#structs).\n",
    "\n",
    "The builtin struct type `PauliTerm` is defined as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "@struct\n",
    "class PauliTerm:\n",
    "    pauli: CArray[Pauli]\n",
    "    coefficient: CReal"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that `Pauli` is an enum for all the Pauli matrices (I, X, Y, Z).\n",
    "\n",
    "Pauli based hamiltonian can be represented as a list of `PauliTerm`s. A Pauli operator defined this way is the argument to a hamiltonian evolution functions.\n",
    "\n",
    "In this exercise we will use the Suzuki-Trotter function to find the evolution of `H=0.5XZXX + 0.25YIZI + 0.3 XIZY` (captured as a literal value for the pauli-operator), with the evolution coefficient being 3, the order being 2, and use 4 repetitions.\n",
    "\n",
    "The declaration of the `suzuki_trotter` function is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc(external=True)\n",
    "def suzuki_trotter(\n",
    "    pauli_operator: CArray[PauliTerm],\n",
    "    evolution_coefficient: CReal,\n",
    "    order: CInt,\n",
    "    repetitions: CInt,\n",
    "    qbv: QArray[QBit],\n",
    ") -> None:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fill in the missing parts of the following code in order to complete this exercise:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/80c5a839-8aae-4997-af10-c8b6645f0c60?version=0.41.1\n"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def main(q: Output[QArray[QBit]]) -> None:\n",
    "    allocate(4, q)\n",
    "    suzuki_trotter(\n",
    "        [\n",
    "            PauliTerm(pauli=[Pauli.X, Pauli.Z, Pauli.X, Pauli.X], coefficient=0.5),\n",
    "            PauliTerm(pauli=[Pauli.Y, Pauli.I, Pauli.Z, Pauli.I], coefficient=0.25),\n",
    "            PauliTerm(pauli=[Pauli.X, Pauli.I, Pauli.Z, Pauli.Y], coefficient=0.3),\n",
    "        ],\n",
    "        evolution_coefficient=3,\n",
    "        repetitions=4,\n",
    "        order=2,\n",
    "        qbv=q,\n",
    "    )\n",
    "\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 7 - Basic Arithmetics\n",
    "\n",
    "#### Exercise 7a\n",
    "In this exercise we will use quantum numeric variables and calculate expressions over them.\n",
    "\n",
    "See details on the syntax of numeric types under [Quantum types](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/quantum-types/#syntax).\n",
    "See more on quantum expressions under [Numeric assignment](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/numeric-assignment/)\n",
    "\n",
    "Create the following quantum programs:\n",
    "1. Initialize variables `x=2`, `y=7` and computes `res = x + y`.\n",
    "2. Initialize variables `x=2`, `y=7` and computes `res = x * y`.\n",
    "3. Initialize variables `x=2`, `y=7`, `z=1` and computes `res = x * y - z`.\n",
    "\n",
    "Guidance:\n",
    "* Use the operator `|=` to perform out-of-place assignment of arithmetic expression.\n",
    "* To initialize the variables, use the function `prepare_int`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/a7aec343-f19c-4a69-859e-dc4d7876a8ec?version=0.41.1\n"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "# Your code here:\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QNum], y: Output[QNum], res: Output[QNum]) -> None:\n",
    "    prepare_int(2, x)\n",
    "    prepare_int(7, y)\n",
    "    res |= x + y\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/7c8f2135-1669-4e24-9608-bf68ea70a29f?version=0.41.1\n"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "# Your code here:\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QNum], y: Output[QNum], res: Output[QNum]) -> None:\n",
    "    prepare_int(2, x)\n",
    "    prepare_int(7, y)\n",
    "    res |= x * y\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/b276f7d5-89a5-487b-adcc-aa49ed5e9d8a?version=0.41.1\n"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "# Your code here:\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QNum], y: Output[QNum], z: Output[QNum], res: Output[QNum]) -> None:\n",
    "    prepare_int(2, x)\n",
    "    prepare_int(7, y)\n",
    "    prepare_int(1, z)\n",
    "    res |= x + y - z\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise 7b\n",
    "Declare `x` to be a 2-qubit variable and `y` to be 3-qubit variable.\n",
    "\n",
    "We will perform an addition of two superposition states: `x` is an equal superposition of `0` and `2`, and `y` is an equal superposition of `1`, `2`, `3`, and `6`.\n",
    "\n",
    "1. Use `prepare_state` to initialize `x` and `y`. Note that `prepare_state` works with probabilities, not amplitudes.\n",
    "   The declaration of the `prepare_state` function is:\n",
    "   ```\n",
    "        @qfunc(external=True)\n",
    "        def prepare_state(\n",
    "            probabilities: CArray[CReal],\n",
    "            bound: CReal,\n",
    "            out: Output[QArray[QBit]],\n",
    "        ) -> None:\n",
    "            pass\n",
    "   ```\n",
    "   (Set the bound to 0 in your code)\n",
    "2. Compute `res = x + y`. Execute the resulting circuit. What did you get?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "unsupported operand type(s) for +: 'QArray' and 'QArray'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_10168\\4234725309.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     17\u001b[0m \u001b[1;31m# Your code here:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     18\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 19\u001b[1;33m \u001b[0mqmod\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mcreate_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmain\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     20\u001b[0m \u001b[0mqprog\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0msynthesize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mqmod\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     21\u001b[0m \u001b[0mshow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mqprog\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\classiq\\qmod\\quantum_function.py\u001b[0m in \u001b[0;36mcreate_model\u001b[1;34m(entry_point, constraints, execution_preferences, preferences, classical_execution_function)\u001b[0m\n\u001b[0;32m     41\u001b[0m             \u001b[1;34mf\"The entry point function must be named 'main', got '{entry_point.func_decl.name}'\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     42\u001b[0m         )\n\u001b[1;32m---> 43\u001b[1;33m     return entry_point.create_model(\n\u001b[0m\u001b[0;32m     44\u001b[0m         \u001b[0mconstraints\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mexecution_preferences\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpreferences\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mclassical_execution_function\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     45\u001b[0m     ).get_model()\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\classiq\\qmod\\quantum_function.py\u001b[0m in \u001b[0;36mcreate_model\u001b[1;34m(self, constraints, execution_preferences, preferences, classical_execution_function)\u001b[0m\n\u001b[0;32m     76\u001b[0m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_qmodule\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mconstants\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     77\u001b[0m         \u001b[0mQConstant\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mset_current_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_qmodule\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 78\u001b[1;33m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_add_native_func_def\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     79\u001b[0m         model_extra_settings: List[Tuple[str, Any]] = [\n\u001b[0;32m     80\u001b[0m             \u001b[1;33m(\u001b[0m\u001b[1;34m\"constraints\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mconstraints\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\classiq\\qmod\\quantum_function.py\u001b[0m in \u001b[0;36m_add_native_func_def\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m     97\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc_decl\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_qmodule\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnative_defs\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     98\u001b[0m             \u001b[1;32mreturn\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 99\u001b[1;33m         \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexpand\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    100\u001b[0m         self._qmodule.native_defs[self.func_decl.name] = NativeFunctionDefinition(\n\u001b[0;32m    101\u001b[0m             \u001b[1;33m**\u001b[0m\u001b[1;33m{\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc_decl\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[1;33m{\u001b[0m\u001b[1;34m\"body\"\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbody\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\classiq\\qmod\\quantum_expandable.py\u001b[0m in \u001b[0;36mexpand\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m     84\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mQExpandable\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mSTACK\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     85\u001b[0m             \u001b[1;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 86\u001b[1;33m                 \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_py_callable\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_positional_args\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     87\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     88\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0minfer_rename_params\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m->\u001b[0m \u001b[0mDict\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_10168\\4234725309.py\u001b[0m in \u001b[0;36mmain\u001b[1;34m(x, y, res)\u001b[0m\n\u001b[0;32m     10\u001b[0m     \u001b[0mprepare_state\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mprobabilities\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mprobabilities2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mbound\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m0.01\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mout\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     11\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 12\u001b[1;33m     \u001b[0mres\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mx\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     13\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     14\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'QArray' and 'QArray'"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "# Your code here:\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QArray[QBit]],y: Output[QArray[QBit]],res: Output[QArray[QBit]]):\n",
    "    probabilities1 = [1/2, 0, 0, 1/2]\n",
    "    prepare_state(probabilities=probabilities1, bound=0.01, out=x)\n",
    "    probabilities2 = [0,1/4,1/4,1/4, 0, 0, 1/4,0]\n",
    "    prepare_state(probabilities=probabilities2, bound=0.01, out=y)\n",
    "    \n",
    "    res = x + y\n",
    "\n",
    "    \n",
    "    \n",
    "    \n",
    "# Your code here:\n",
    "    \n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 8 - Within-Apply\n",
    "\n",
    "The within-apply statement applies the pattern `U_dagger V U` that appears frequently in quantum computing.\n",
    "It allows you to compute some function `V` within the context of another function `U`, and afterward uncompute `U` in order to release auxiliary qubits storing intermediate results.\n",
    "\n",
    "See also [Within Apply](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/within-apply/).\n",
    "\n",
    "#### Exercise 8a\n",
    "\n",
    "In this exercise, we will use within-apply to compute an arithmetic expression in steps.\n",
    "\n",
    "Use the `within_apply` operation to calculate `res = x + y + z` from a two-variable addition building block with the following steps:\n",
    "1. Add `x` and `y`\n",
    "2. Add the result to `z`\n",
    "3. Uncompute the result of the first operation\n",
    "\n",
    "For simplicity, initialize the registers to simple integers: `x=3`, `y=5`, `z=2`.\n",
    "\n",
    "Hints:\n",
    "\n",
    "* Use a temporary variable.\n",
    "* Wrap the arithmetic operation in a function.\n",
    "\n",
    "Execute the circuit and make sure you obtain the expected result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValidationError",
     "evalue": "1 validation error for Model\n__root__\n  Handle 'x' was errored with \"At the end of the function, in port x is expected to be HandleState.INITIALIZED but it isn't\"\nHandle 'y' was errored with \"At the end of the function, in port y is expected to be HandleState.INITIALIZED but it isn't\"\nIf you need further assistance, please reach out on our Community Slack channel at: https://short.classiq.io/join-slack\nIf the error persists feel free to open a ticket at: https://short.classiq.io/support (type=value_error.classiqvalue; _raw_message=Handle 'x' was errored with \"At the end of the function, in port x is expected to be HandleState.INITIALIZED but it isn't\"\nHandle 'y' was errored with \"At the end of the function, in port y is expected to be HandleState.INITIALIZED but it isn't\")",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValidationError\u001b[0m                           Traceback (most recent call last)",
      "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_10168\\4146049375.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     19\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     20\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 21\u001b[1;33m \u001b[0mqmod\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mcreate_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmain\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     22\u001b[0m \u001b[0mqprog\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0msynthesize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mqmod\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     23\u001b[0m \u001b[0mshow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mqprog\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\classiq\\qmod\\quantum_function.py\u001b[0m in \u001b[0;36mcreate_model\u001b[1;34m(entry_point, constraints, execution_preferences, preferences, classical_execution_function)\u001b[0m\n\u001b[0;32m     41\u001b[0m             \u001b[1;34mf\"The entry point function must be named 'main', got '{entry_point.func_decl.name}'\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     42\u001b[0m         )\n\u001b[1;32m---> 43\u001b[1;33m     return entry_point.create_model(\n\u001b[0m\u001b[0;32m     44\u001b[0m         \u001b[0mconstraints\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mexecution_preferences\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpreferences\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mclassical_execution_function\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     45\u001b[0m     ).get_model()\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\classiq\\qmod\\quantum_function.py\u001b[0m in \u001b[0;36mcreate_model\u001b[1;34m(self, constraints, execution_preferences, preferences, classical_execution_function)\u001b[0m\n\u001b[0;32m     87\u001b[0m                 \u001b[1;33m(\u001b[0m\u001b[1;34m\"classical_execution_code\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mclassical_execution_function\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcode\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     88\u001b[0m             )\n\u001b[1;32m---> 89\u001b[1;33m         return Model(\n\u001b[0m\u001b[0;32m     90\u001b[0m             \u001b[0mconstants\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlist\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_qmodule\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mconstants\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     91\u001b[0m             \u001b[0mfunctions\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlist\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_qmodule\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnative_defs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\pydantic\\main.cp39-win_amd64.pyd\u001b[0m in \u001b[0;36mpydantic.main.BaseModel.__init__\u001b[1;34m()\u001b[0m\n",
      "\u001b[1;31mValidationError\u001b[0m: 1 validation error for Model\n__root__\n  Handle 'x' was errored with \"At the end of the function, in port x is expected to be HandleState.INITIALIZED but it isn't\"\nHandle 'y' was errored with \"At the end of the function, in port y is expected to be HandleState.INITIALIZED but it isn't\"\nIf you need further assistance, please reach out on our Community Slack channel at: https://short.classiq.io/join-slack\nIf the error persists feel free to open a ticket at: https://short.classiq.io/support (type=value_error.classiqvalue; _raw_message=Handle 'x' was errored with \"At the end of the function, in port x is expected to be HandleState.INITIALIZED but it isn't\"\nHandle 'y' was errored with \"At the end of the function, in port y is expected to be HandleState.INITIALIZED but it isn't\")"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "@qfunc\n",
    "def prep_plus(x: Output[QNum], y: Output[QNum] , Add: Output[QNum]) -> None:\n",
    "    Add |= x+y \n",
    "\n",
    "@qfunc\n",
    "def main_plus(Add: Output[QNum], z: Output[QNum] , res: Output[QNum]) -> None:\n",
    "    res |= Add+z \n",
    "    \n",
    "    \n",
    "@qfunc\n",
    "def main(x: Output[QNum], y: Output[QNum] , z: Output[QNum], Add: Output[QNum], res: Output[QNum]) -> None:\n",
    "    prepare_int(3, x)\n",
    "    prepare_int(5, y)\n",
    "    prepare_int(1, z)\n",
    "    \n",
    "    within_apply(lambda: prep_plus(x,y,Add), lambda: main_plus(Add,z,res))\n",
    "\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise 8b\n",
    "\n",
    "Why should we use `within-apply` and not just write three concatenated functions?\n",
    "To understand the motivation, we will create another arithmetic circuit.\n",
    "This time, however, we will also set Classiq’s synthesis engine to optimize on the circuit’s number of qubits, i.e., its width.\n",
    "\n",
    "Setting constraints can be done via the `set_constraints` operation - see [here](https://docs.classiq.io/latest/user-guide/platform/synthesis/constraints/).\n",
    "\n",
    "Perform the operation `res = w + x + y + z`, where w is initialized to 4 and the rest as before:\n",
    "\n",
    "1. Add `x` and `y` (as part of the `within_apply` operation)\n",
    "2. Add the result to `z` (as part of the within_apply operation)\n",
    "3. Uncompute the result of the first operation (as part of the `within_apply` operation)\n",
    "4. Add the result of the second operation to `w`. There’s no need to perform another uncomputation, as this brings our calculation to an end.\n",
    "\n",
    "Create the model, optimize on the circuit’s width, and run the circuit. Can you identify where qubits have been released and reused?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *\n",
    "\n",
    "# Your code here:\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Bonus: Use a Single Arithmetic Expression\n",
    "\n",
    "What happens when we don't manually decompose this expression?\n",
    "\n",
    "Use Classiq’s arithmetic engine to calculate `res |= x + y + z + w` and optimize for width.\n",
    "Look at the resulting quantum program - can you identify the computation and uncomputation blocks? What else did you notice?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *\n",
    "\n",
    "# Your code here:\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 9 - In-place Arithmetics\n",
    "\n",
    "For the following exercise we will use numeric quantum variables that represent fixed-point reals.\n",
    "\n",
    "Arithmetic expressions can be calculated in-place into a target variable, without allocating new qubits to store the result. This is done using the in-place-xor operator.\n",
    "\n",
    "See also [Numeric assignment](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/numeric-assignment/#semantics).\n",
    "\n",
    "In-place assignment is often used to nest arithmetic expressions under quantum operators. Note that out-of-place assignment requires its left-value variable to be un-initialized, and therefore cannot be used under an operator if the variable is declared outside its scope. Applying operators to arithmetic expressions is required in many algorithms. One example is the piecewise evaluation of mathematical functions - calculating different expressions over `x` depending on the subdomain where `x` falls.\n",
    "\n",
    "For this exercise, replace the missing parts in the code snippet below to evaluate the result of:\n",
    "\n",
    "$$\n",
    "f(x) = \\begin{cases}\n",
    "      2x + 1 & \\text{ if } 0 \\leq x < 0.5 \\\\\n",
    "      x + 0.5 & \\text{ if } 0.5 \\leq x < 1\n",
    "   \\end{cases}\n",
    "$$\n",
    "\n",
    "Notes:\n",
    "- We cannot use `x` directly as the control variable in a `constrol` operator, because it also occurs in the nested scope. to determine if `x` is in the lower or higher half of the domain we duplicate the most significant bit onto a separate variable called `label`.\n",
    "- In Python assignment operators cannot be used in lambda expressions, so the computation of the function needs to be factored out to a named Python function (but not necessarily a Qmod function).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/820a33c8-8d74-40f1-910d-bc58a9036ae0?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "\n",
    "from classiq import *\n",
    "\n",
    "\n",
    "def linear_func(a: float, b: float, x: QNum, res: QNum) -> None:\n",
    "    res ^= a * x + b\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def dup_msb(qba: QArray[QBit], msb: QBit) -> None:\n",
    "    CX(qba[qba.len - 1], msb)\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QNum[3, False, 3]], res: Output[QNum[5, False, 3]]) -> None:\n",
    "    allocate(5, res)\n",
    "    allocate(3, x)\n",
    "    hadamard_transform(x)\n",
    "\n",
    "    label = QArray(\"label\")\n",
    "    allocate(1, label)\n",
    "\n",
    "    dup_msb(x, label)\n",
    "    control(label, lambda: linear_func(1.0, 0.5, x, res))  # 0.5 <= x < 1.0\n",
    "    X(label)\n",
    "    control(label, lambda: linear_func(2.0, 1.0, x, res))  # 0.0 <= x < 0.5\n",
    "\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 10 - State-preparation Algorithm using Quantum-if\n",
    "\n",
    "#### Binding\n",
    "The `bind` operation allows to convert smoothly between different quantum types and split or slice bits when necessary. Here’s an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/7b2bd311-2a76-4bc6-8904-8f470ce01f88?version=0.41.2\n"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "\n",
    "\n",
    "from classiq import *\n",
    "from math import pi\n",
    "\n",
    "\n",
    "@qfunc\n",
    "def main(res: Output[QArray[QBit]]) -> None:\n",
    "    x: QArray[QBit] = QArray(\"x\")\n",
    "    allocate(3, x)\n",
    "    hadamard_transform(x)\n",
    "\n",
    "    lsb = QBit(\"lsb\")\n",
    "    msb = QNum(\"msb\", 2, False, 0)\n",
    "    bind(x, [lsb, msb])\n",
    "\n",
    "    control(msb == 1, lambda: RY(pi / 3, lsb))   # msb==1 <==> bit1 bit2 == 01 (binary of decimal 1)\n",
    "\n",
    "    bind([lsb, msb], res)\n",
    "\n",
    "model = create_model(main)\n",
    "qprog = synthesize(model)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first `bind` operation splits the 3-qubit register `x` into the 2-qubit and single-qubit registers `lsb` and `msb`, respectively.\n",
    "\n",
    "After the `bind` operation:\n",
    "1. The registers `lsb` and `msb` can be operated on as separated registers.\n",
    "2. The register`x` is consumed and can no longer be used.\n",
    "\n",
    "The second `bind` operation concatenates the registers to the output register `res`.\n",
    "\n",
    "For this exercise, fill in the missing code parts in the above snippet and use the `control` statement to manually generate the following lovely 3-qubit probability distribution: `[1/8, 1/8, 1/8, -sqrt(3)/16, 1/8 + sqrt(3)/16, 1/8, 1/8, 1/8, 1/8]`.\n",
    "\n",
    "The following series of gates generate it:\n",
    "\n",
    "Perform the Hadamard transform on all three qubits.\n",
    "\n",
    "Apply a rotation on the LSB (least-significant bit) conditioned by the MSB being |0> and the second to last MSB being |1>. How would you write this condition using a QNum?\n",
    "\n",
    "The following series of gates generate it:\n",
    "1. Perform the Hadamard transform on all three qubits.\n",
    "2. Apply a `pi/3` rotation on the LSB (least-significant bit) conditioned by the MSB being |0> and the second to last MSB being |1>. How would you write this condition using a QNum?\n",
    "\n",
    "If you want to validate your results without looking at the full solution, compare them to running using Classiq’s built-in `prepare_state` function.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *\n",
    "from classiq.qmod.symbolic import sqrt\n",
    "\n",
    "prob_list= [\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            -sqrt(3) / 16,\n",
    "            1 / 8 + sqrt(3) / 16,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "        ]\n",
    "@qfunc\n",
    "def pre_prepared_state(q: QArray[QBit]) -> None:\n",
    "    prepare_state(\n",
    "        [\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            -sqrt(3) / 16,\n",
    "            1 / 8 + sqrt(3) / 16,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "            1 / 8,\n",
    "        ],\n",
    "        0.0,\n",
    "        q,\n",
    "    )\n",
    "\n",
    "\n",
    "# Your code here:"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
